//--------------------------------------------------------------------------
//	Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are
//  met:
//
//  Redistributions of source code must retain the above copyright notice,
//  this list of conditions and the following disclaimer.
//  Redistributions in binary form must reproduce the above copyright
//  notice, this list of conditions and the following disclaimer in the
//  documentation and/or other materials provided with the distribution.
//  Neither the name of the Drew Davidson nor the names of its contributors
//  may be used to endorse or promote products derived from this software
//  without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
//  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
//  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
//  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
//  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
//  DAMAGE.
//--------------------------------------------------------------------------
package ariba.util.expr;

import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import ariba.util.fieldtype.PrimitiveTypeProvider;

/**
 * This is an abstract class with static methods that define runtime
 * caching information in AribaExpr.
 * @author Luke Blanshard (blanshlu@netscape.net)
 * @author Drew Davidson (drew@ognl.org)
 */
public abstract class ExprRuntime extends Object
{
    public static final Object              NotFound = new Object();
    public static final List                NotFoundList = new ArrayList();
    public static final Map                 NotFoundMap = new HashMap();
    public static final Object[]            NoArguments = new Object[] {};
    public static final Class[]             NoArgumentTypes = new Class[] {};

    /** Token returned by TypeConverter for no conversion possible */
    public static final Object              NoConversionPossible = "AribaExpr.NoConversionPossible";

    /** Not an indexed property */
    public static int                       INDEXED_PROPERTY_NONE = 0;
    /** JavaBeans IndexedProperty */
    public static int                       INDEXED_PROPERTY_INT = 1;
    /** AribaExpr ObjectIndexedProperty */
    public static int                       INDEXED_PROPERTY_OBJECT = 2;

    // NULL_STRING give a string displaying "null".
    public static final String              NULL_DISPLY_STRING = "" + null;

    // NULL_STRING_FIELD is a value when null is casted to a string.
    public static final String              NULL_VALUE_STRING = "";

    private static final String             SET_PREFIX = "set";
    private static final String             GET_PREFIX = "get";
    private static final String             IS_PREFIX = "is";

    /**
        Prefix padding for hexadecimal numbers to HEX_LENGTH.
     */
    private static final Map			    HEX_PADDING = new HashMap();

    /**
        Hexadecimal prefix for printing "pointers".
     */
    private static final String			    HEX_PREFIX = "0x";

    private static final int				HEX_LENGTH = 8;
    /**
        Returned by <CODE>getUniqueDescriptor()</CODE> when the
        object is <CODE>null</CODE>.
     */
    private static final String      	    NULL_OBJECT_STRING = "<null>";


    private static ClassCache               methodAccessors = new ClassCache();
    private static ClassCache               elementsAccessors = new ClassCache();
    private static ClassCache               nullHandlers = new ClassCache();
    private static ClassCache               propertyDescriptorCache = new ClassCache();
    private static ClassCache               constructorCache = new ClassCache();
    private static ClassCache               staticMethodCache = new ClassCache();
    private static ClassCache               instanceMethodCache = new ClassCache();
    private static ClassCache               invokePermissionCache = new ClassCache();
    private static ClassCache               fieldCache = new ClassCache();
    private static List                     superclasses = new ArrayList(); /* Used by fieldCache lookup */
    private static ClassCache[]             declaredMethods = new ClassCache[] { new ClassCache(), new ClassCache() };   /* set, get */
    private static Map                      primitiveTypes = new HashMap(101);
    private static ClassCache               primitiveDefaults = new ClassCache();
    private static Map                      methodParameterTypesCache = new HashMap(101);
    private static Map                      ctorParameterTypesCache = new HashMap(101);
    private static SecurityManager          securityManager = null; // System.getSecurityManager();
    private static EvaluationPool           evaluationPool = new EvaluationPool();
    private static ObjectArrayPool          objectArrayPool = new ObjectArrayPool();

    /**
        This is a highly specialized map for storing values keyed by Class objects.
     */
    private static class ClassCache extends Object
    {
        /* this MUST be a power of 2 */
        private static final int    TABLE_SIZE = 512;

        /* ...and now you see why.  The table size is used as a mask for generating hashes */
        private static final int    TABLE_SIZE_MASK = TABLE_SIZE - 1;

        private Entry[]             table;

        private static class Entry extends Object
        {
            protected Entry                 next;
            protected Class                 key;
            protected Object                value;

            public Entry(Class key, Object value)
            {
                super();
                this.key = key;
                this.value = value;
            }
        }

        public ClassCache()
        {
            super();
            this.table = new Entry[TABLE_SIZE];
        }

        public final Object get(Class key)
        {
            Object      result = null;
            int         i = key.hashCode() & TABLE_SIZE_MASK;

            for (Entry entry = table[i]; entry != null; entry = entry.next) {
                if (entry.key == key) {
                    result = entry.value;
                    break;
                }
            }
            return result;
        }

        public final Object put(Class key, Object value)
        {
            Object      result = null;
            int         i = key.hashCode() & TABLE_SIZE_MASK;
            Entry       entry = table[i];

            if (entry == null) {
                table[i] = new Entry(key, value);
            } else {
                if (entry.key == key) {
                    result = entry.value;
                    entry.value = value;
                } else {
                    while (true) {
                        if (entry.key == key) {
                            /* replace value */
                            result = entry.value;
                            entry.value = value;
                            break;
                        } else {
                            if (entry.next == null) {
                                /* add value */
                                entry.next = new Entry(key, value);
                                break;
                            }
                        }
                        entry = entry.next;
                    }
                }
            }
            return result;
        }
    }

    static
    {
/*  We're missing these in util.fieldvalue.OrderedList...
        setElementsAccessor( Map.class, new MapElementsAccessor() );   // Over values
        setElementsAccessor( Number.class, new NumberElementsAccessor() ); // Integers, 0 ...
        setElementsAccessor( Object.class, new ObjectElementsAccessor() ); // Iterator over one element -- target obj
        setElementsAccessor( Iterator.class, new IteratorElementsAccessor() );
        setElementsAccessor( Enumeration.class, new EnumerationElementsAccessor() );
*/

        NullHandler nh = new ObjectNullHandler();
        setNullHandler( Object.class,  nh);
        setNullHandler( byte[].class, nh );
        setNullHandler( short[].class, nh );
        setNullHandler( char[].class, nh );
        setNullHandler( int[].class, nh );
        setNullHandler( long[].class, nh );
        setNullHandler( float[].class, nh );
        setNullHandler( double[].class, nh );
        setNullHandler( Object[].class, nh );

        MethodAccessor  ma = new ObjectMethodAccessor();
        setMethodAccessor( Object.class, ma );
        setMethodAccessor( byte[].class, ma );
        setMethodAccessor( short[].class, ma );
        setMethodAccessor( char[].class, ma );
        setMethodAccessor( int[].class, ma );
        setMethodAccessor( long[].class, ma );
        setMethodAccessor( float[].class, ma );
        setMethodAccessor( double[].class, ma );
        setMethodAccessor( Object[].class, ma );

        primitiveTypes.put("boolean", Boolean.TYPE );
        primitiveTypes.put("byte", Byte.TYPE );
        primitiveTypes.put("short", Short.TYPE );
        primitiveTypes.put("char", Character.TYPE );
        primitiveTypes.put("int", Integer.TYPE );
        primitiveTypes.put("long", Long.TYPE );
        primitiveTypes.put("float", Float.TYPE );
        primitiveTypes.put("double", Double.TYPE );

        primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
        primitiveDefaults.put(Byte.TYPE, new Byte((byte)0));
        primitiveDefaults.put(Short.TYPE, new Short((short)0));
        primitiveDefaults.put(Character.TYPE, new Character((char)0));
        primitiveDefaults.put(Integer.TYPE, new Integer(0));
        primitiveDefaults.put(Long.TYPE, new Long(0L));
        primitiveDefaults.put(Float.TYPE, new Float(0.0f));
        primitiveDefaults.put(Double.TYPE, new Double(0.0));
        primitiveDefaults.put(BigInteger.class, new BigInteger("0"));
        primitiveDefaults.put(BigDecimal.class, new BigDecimal(0.0));
    }

    /**
        Gets the "target" class of an object for looking up accessors that
        are registered on the target.  If the object is a Class object this
        will return the Class itself, else it will return object's getClass()
        result.
     */
    public static Class getTargetClass(Object o)
    {
        return (o == null) ? null : ((o instanceof Class) ? (Class)o : o.getClass());
    }

    /**
        Returns the base name (the class name without the
        package name prepended) of the object given.
     */
    public static String getBaseName(Object o)
    {
        return (o == null) ? null : getClassBaseName(o.getClass());
    }

    /**
        Returns the base name (the class name without the
        package name prepended) of the class given.
     */
    public static String getClassBaseName(Class c)
    {
        String      s = c.getName();

        return s.substring(s.lastIndexOf('.') + 1);
    }

    public static String getClassName(Object o, boolean fullyQualified)
    {
        if (!(o instanceof Class)) {
            o = o.getClass();
        }
        return getClassName((Class)o, fullyQualified);
    }

    public static String getClassName(Class c, boolean fullyQualified)
    {
        return fullyQualified ? c.getName() : getClassBaseName(c);
    }

    /**
        Returns the package name of the object's class.
     */
    public static String getPackageName(Object o)
    {
        return (o == null) ? null : getClassPackageName(o.getClass());
    }

    /**
        Returns the package name of the class given.
     */
    public static String getClassPackageName(Class c)
    {
        String      s = c.getName();
        int         i = s.lastIndexOf('.');

        return (i < 0) ? null : s.substring(0, i);
    }

    /**
        Returns a "pointer" string in the usual format for these
        things - 0x<hex digits>.
     */
    public static String getPointerString(int num)
    {
        StringBuffer	result = new StringBuffer();
        String			hex = Integer.toHexString(num),
                        pad;
        Integer			l = new Integer(hex.length());

        //result.append(HEX_PREFIX);
        if ((pad = (String)HEX_PADDING.get(l)) == null) {
            StringBuffer	pb = new StringBuffer();

            for (int i = hex.length(); i < HEX_LENGTH; i++) {
                pb.append('0');
            }
            pad = new String(pb);
            HEX_PADDING.put(l, pad);
        }
        result.append(pad);
        result.append(hex);
        return new String(result);
    }

    /**
        Returns a "pointer" string in the usual format for these
        things - 0x<hex digits> for the object given.  This will
        always return a unique value for each object.
     */
    public static String getPointerString(Object o)
    {
        return getPointerString((o == null) ? 0 : System.identityHashCode(o));
    }

    /**
        Returns a unique descriptor string that includes the object's
        class and a unique integer identifier.  If fullyQualified is
        true then the class name will be fully qualified to include
        the package name, else it will be just the class' base name.
     */
    public static String getUniqueDescriptor(Object object, boolean fullyQualified)
    {
        StringBuffer	result = new StringBuffer();

        if (object != null) {
            if (object instanceof Proxy) {
                Class		interfaceClass = object.getClass().getInterfaces()[0];

                result.append(getClassName(interfaceClass, fullyQualified));
                result.append('^');
                object = Proxy.getInvocationHandler(object);
            }
            result.append(getClassName(object, fullyQualified));
            result.append('@');
            result.append(getPointerString(object));
        } else {
            result.append(NULL_OBJECT_STRING);
        }
        return new String(result);
    }

    /**
        Returns a unique descriptor string that includes the object's
        class' base name and a unique integer identifier.
     */
    public static String getUniqueDescriptor(Object object)
    {
        return getUniqueDescriptor(object, false);
    }

    /**
        Utility to convert a List into an Object[] array.  If the list is zero
        elements this will return a constant array; toArray() on List always
        returns a new object and this is wasteful for our purposes.
     */
    public static Object[] toArray(List list)
    {
        Object[]        result;
        int             size = list.size();

        if (size == 0) {
            result = NoArguments;
        } else {
            result = getObjectArrayPool().create(list.size());
            for (int i = 0; i < size; i++) {
                result[i] = list.get(i);
            }
        }
        return result;
    }

    /**
        Returns the parameter types of the given method.
     */
    public static Class[] getParameterTypes(Method m)
    {
        synchronized(methodParameterTypesCache) {
            Class[]     result;

            if ((result = (Class[])methodParameterTypesCache.get(m)) == null) {
                methodParameterTypesCache.put(m, result = m.getParameterTypes());
            }
            return result;
        }
    }

    /**
        Returns the parameter types of the given method.
     */
    public static Class[] getParameterTypes(Constructor c)
    {
        synchronized(ctorParameterTypesCache) {
            Class[]     result;

            if ((result = (Class[])ctorParameterTypesCache.get(c)) == null) {
                ctorParameterTypesCache.put(c, result = c.getParameterTypes());
            }
            return result;
        }
    }

    /**
     * Gets the SecurityManager that AribaExpr uses to determine permissions for
     * invoking methods.
     *
     * @return SecurityManager for AribaExpr
     */
    public static SecurityManager getSecurityManager()
    {
        return securityManager;
    }

    /**
     * Sets the SecurityManager that AribaExpr uses to determine permissions for
     * invoking methods.
     *
     * @param value SecurityManager to set
     */
    public static void setSecurityManager(SecurityManager value)
    {
        securityManager = value;
    }

    /**
        Permission will be named "invoke.<declaring-class>.<method-name>".
     */
    public static Permission getPermission(Method method)
    {
        Permission              result = null;
        Class                   mc = method.getDeclaringClass();

        synchronized(invokePermissionCache) {
            Map                     permissions = (Map)invokePermissionCache.get(mc);

            if (permissions == null) {
                invokePermissionCache.put(mc, permissions = new HashMap(101));
            }
            if ((result = (Permission)permissions.get(method.getName())) == null) {
                result = new ExprInvokePermission("invoke." + mc.getName() + "." + method.getName());
                permissions.put(method.getName(), result);
            }
        }
        return result;
    }

    public static Object invokeMethod( Object target, Method method, Object[] argsArray ) throws InvocationTargetException, IllegalAccessException
    {
        Object      result;
        boolean     wasAccessible = true;

        if (securityManager != null) {
            try {
                securityManager.checkPermission(getPermission(method));
            } catch (SecurityException ex) {
                throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
            }
        }
        if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
            if (!(wasAccessible = ((AccessibleObject)method).isAccessible())) {
                ((AccessibleObject)method).setAccessible(true);
            }
        }
        result = method.invoke( target, argsArray );
        if (!wasAccessible) {
            ((AccessibleObject)method).setAccessible(false);
        }
        return result;
    }

      /**
       * Gets the class for a method argument that is appropriate for looking up methods
       * by reflection, by looking for the standard primitive wrapper classes and
       * exchanging for them their underlying primitive class objects.  Other classes are
       * passed through unchanged.
       *
       * @param arg an object that is being passed to a method
       * @return the class to use to look up the method
       */
    public static final Class getArgClass( Object arg )
    {
        if ( arg == null )
            return null;
        Class c = arg.getClass();
        if ( c == Boolean.class )
            return Boolean.TYPE;
        else if ( c.getSuperclass() == Number.class ) {
            if ( c == Integer.class )
                return Integer.TYPE;
            if ( c == Double.class )
                return Double.TYPE;
            if ( c == Byte.class )
                return Byte.TYPE;
            if ( c == Long.class )
                return Long.TYPE;
            if ( c == Float.class )
                return Float.TYPE;
            if ( c == Short.class )
                return Short.TYPE;
        }
        else if ( c == Character.class )
            return Character.TYPE;
        return c;
    }

    /**
     * Tells whether the given object's type can be widen to the given class
     * ---that is, whether the given object can be passed as an argument
     * to a method or constructor with type widdening conversion.
     */
  public static final boolean canWideningTo( Object object, Class c )
  {
      boolean         result = false;

      if ( object != null ) {
          if (TypeConversionHelper.canConvert(c, object)) {
              result = true;
          }
      }
      else {
          // If object is null, class cannot be a primitive type.
          result = !c.isPrimitive();
      }
      return result;
  }

    /**
     * Tells whether the given array of objects is compatible with the given array of
     * classes---that is, whether the given array of objects can be passed as arguments
     * to a method or constructor whose parameter types are the given array of classes.
     */
  public static final boolean canArgsWideningTo( Object[] args, Class[] classes )
  {
      boolean     result = true;

      if ( args.length != classes.length ) {
          result = false;
      } else {
          for ( int index=0, count=args.length; result && (index < count); ++index ) {
            result = canWideningTo(args[index], classes[index]);
          }
      }
      return result;
  }


      /**
       * Tells whether the given object is compatible with the given class
       * ---that is, whether the given object can be passed as an argument
       * to a method or constructor whose parameter type is the given class.
       * If object is null this will return true because null is compatible
       * with any type.
       */
    public static final boolean isTypeCompatible( Object object, Class c )
    {
        boolean         result = true;

        if ( object != null ) {
            if ( c.isPrimitive() ) {
                if ( getArgClass(object) != c) {
                    result = false;
                }
            } else if ( !c.isInstance(object) ) {
                result = false;
            }
        }
        return result;
    }

      /**
       * Tells whether the given array of objects is compatible with the given array of
       * classes---that is, whether the given array of objects can be passed as arguments
       * to a method or constructor whose parameter types are the given array of classes.
       */
    public static final boolean areArgsCompatible( Object[] args, Class[] classes )
    {
        boolean     result = true;

        if ( args.length != classes.length ) {
            result = false;
        } else {
            for ( int index=0, count=args.length; result && (index < count); ++index ) {
              result = isTypeCompatible(args[index], classes[index]);
            }
        }
        return result;
    }

      /**
       * Tells whether the first array of classes is more specific than the second.
       * Assumes that the two arrays are of the same length.
       */
    public static final boolean isMoreSpecific( Class[] classes1, Class[] classes2 )
    {
        for ( int index=0, count=classes1.length; index < count; ++index )
          {
            Class c1 = classes1[index], c2 = classes2[index];
            if ( c1 == c2 )
                continue;
            else if ( PrimitiveTypeProvider.isSupportedType(c1.getName()) &&
                      PrimitiveTypeProvider.isSupportedType(c2.getName())) {
                // joliang : handle the primtive type widening. If type in
                // classes2 is the widened type, return true.
                return PrimitiveTypeProvider.isWideningTypeOf(
                    c2.getName(), c1.getName());
            }
            else if ( c1.isPrimitive() ) {
                return true;
            } else if ( c1.isAssignableFrom(c2) )
                return false;
            else if ( c2.isAssignableFrom(c1) )
                return true;
          }

          // They are the same!  So the first is not more specific than the second.
        return false;
    }

    public static final String getModifierString(int modifiers)
    {
        String      result;

        if (Modifier.isPublic(modifiers))
            result = "public";
        else
        if (Modifier.isProtected(modifiers))
            result = "protected";
        else
        if (Modifier.isPrivate(modifiers))
            result = "private";
        else
            result = "";
        if (Modifier.isStatic(modifiers))
            result = "static " + result;
        if (Modifier.isFinal(modifiers))
            result = "final " + result;
        if (Modifier.isNative(modifiers))
            result = "native " + result;
        if (Modifier.isSynchronized(modifiers))
            result = "synchronized " + result;
        if (Modifier.isTransient(modifiers))
            result = "transient " + result;
        return result;
    }

    public static final Class classForName( ExprContext context, String className ) throws ClassNotFoundException
    {
        Class           result = (Class)primitiveTypes.get(className);

        if (result == null) {
            ClassResolver   resolver;

            if ((context == null) || ((resolver = context.getClassResolver()) == null)) {
                resolver = ExprContext.DEFAULT_CLASS_RESOLVER;
            }
            result = resolver.classForName(className, context);
        }
        return result;
    }

    public static final boolean isInstance( ExprContext context, Object value, String className ) throws ExprException
    {
        try
          {
            Class c = classForName( context, className);
            return c.isInstance( value );
          }
        catch (ClassNotFoundException e)
          {
            throw new ExprException( "No such class: " + className, e );
          }
    }

    public static Object getPrimitiveDefaultValue( Class forClass )
    {
        return primitiveDefaults.get(forClass);
    }

    public static Object getConvertedType( ExprContext context, Object target, Member member, String propertyName, Object value, Class type)
    {
        return context.getTypeConverter().convertValue(context, target, member, propertyName, value, type);
    }

    public static boolean getConvertedTypes( ExprContext context, Object target, Member member, String propertyName, Class[] parameterTypes, Object[] args, Object[] newArgs)
    {
        boolean         result = false;

        if (parameterTypes.length == args.length) {
            result = true;
            for (int i = 0, ilast = parameterTypes.length - 1; result && (i <= ilast); i++) {
                Object      arg = args[i];
                Class       type = parameterTypes[i];

                if (isTypeCompatible(arg, type)) {
                    newArgs[i] = arg;
                } else {
                    Object      v = getConvertedType(context, target, member, propertyName, arg, type);

                    if (v == ExprRuntime.NoConversionPossible) {
                        result = false;
                    } else {
                        newArgs[i] = v;
                    }
                }
            }
        }
        return result;
    }

    public static Method getConvertedMethodAndArgs( ExprContext context, Object target, String propertyName, List methods, Object[] args, Object[] newArgs)
    {
        Method          result = null;
        TypeConverter   converter = context.getTypeConverter();

        if ((converter != null) && (methods != null)) {
            for (int i = 0, icount = methods.size(); (result == null) && (i < icount); i++) {
                Method      m = (Method)methods.get(i);
                Class[]     parameterTypes = getParameterTypes(m);

                if (getConvertedTypes( context, target, m, propertyName, parameterTypes, args, newArgs )) {
                    result = m;
                }
            }
        }
        return result;
    }

    public static Constructor getConvertedConstructorAndArgs( ExprContext context, Object target, List constructors, Object[] args, Object[] newArgs )
    {
        Constructor     result = null;
        TypeConverter   converter = context.getTypeConverter();

        if ((converter != null) && (constructors != null)) {
            for (int i = 0, icount = constructors.size(); (result == null) && (i < icount); i++) {
                Constructor     ctor = (Constructor)constructors.get(i);
                Class[]         parameterTypes = getParameterTypes(ctor);

                if (getConvertedTypes( context, target, ctor, null, parameterTypes, args, newArgs )) {
                    result = ctor;
                }
            }
        }
        return result;
    }

    /**
        Gets the appropriate method to be called for the given target, method name and arguments.
        If successful this method will return the Method within the target that can be called
        and the converted arguments in actualArgs.  If unsuccessful this method will return
        null and the actualArgs will be empty.
     */
    private static Method getAppropriateMethod( ExprContext context, Object source, Object target, String propertyName, List methods, Object[] args, Object[] actualArgs )
    {
        Method      result = null;
        Class[]     resultParameterTypes = null;

        if (methods != null) {
            for (int i = 0, icount = methods.size(); i < icount; i++) {
                Method  m = (Method)methods.get(i);
                Class[] mParameterTypes = getParameterTypes(m);

                // joliang : when checking method arguments, take into account
                // that the type can be widen.
                if ( canArgsWideningTo(args, mParameterTypes) && ((result == null) || isMoreSpecific(mParameterTypes, resultParameterTypes)) ) {
                    result = m;
                    resultParameterTypes = mParameterTypes;
                    System.arraycopy(args, 0, actualArgs, 0, args.length);
                    for (int j = 0; j < mParameterTypes.length; j++) {
                        Class       type = mParameterTypes[j];

                        // joliang : if it is a supported primitive type, then
                        // convert the type to the parameter type.
                        if (PrimitiveTypeProvider.isSupportedType(type.getName())) {
                            actualArgs[j] = getConvertedType(context, source, result, propertyName, actualArgs[j], type);
                        }
                    }
                }
            }
        }
        if ( result == null ) {
            result = getConvertedMethodAndArgs( context, target, propertyName, methods, args, actualArgs );
        }
        return result;
    }

    public static Object callAppropriateMethod (ExprContext context, Object source,
                                                Object target, String methodName,
                                                Method method, Object[] args)
                                                throws MethodFailedException
    {
        List <Method> methods = null;
        if (method!=null) {
            methods = Collections.singletonList(method);
        }
        return internalCallAppropriateMethod(context,source,target,methodName,null,methods,args);
    }

    //TODO figureout when and why would there be multiple methods.. no caller is passing a list so far.
    private static Object internalCallAppropriateMethod(ExprContext context, Object source, Object target,
                                                        String methodName, String propertyName, List methods, Object[] args )
                                                        throws MethodFailedException
    {
        Throwable   reason = null;
        Object[]    actualArgs = objectArrayPool.create(args.length);

        try {
            Method      method = getAppropriateMethod( context, source, target, propertyName, methods, args, actualArgs );

            if ( (method == null) || !isMethodAccessible(context, source, method, propertyName) )
            {
                StringBuffer        buffer = new StringBuffer();

                if (args != null) {
                    for (int i = 0, ilast = args.length - 1; i <= ilast; i++) {
                        Object      arg = args[i];

                        buffer.append((arg == null) ? NULL_DISPLY_STRING : arg.getClass().getName());
                        if (i < ilast) {
                            buffer.append(", ");
                        }
                    }
                }
                throw new NoSuchMethodException( methodName + "(" + buffer + ")" );
            }
            return invokeMethod(target, method, actualArgs);
          }
        catch (NoSuchMethodException e)
          { reason = e; }
        catch (IllegalAccessException e)
          { reason = e; }
        catch (InvocationTargetException e)
          { reason = e.getTargetException(); }
        finally {
            objectArrayPool.recycle(actualArgs);
        }
        throw new MethodFailedException( source, methodName, reason );
    }

    public static final Object callStaticMethod( ExprContext context, String className, String methodName, Object[] args ) throws ExprException, MethodFailedException
    {
        try {
            Object          result;
            Class           targetClass = classForName(context, className);
            MethodAccessor  ma = getMethodAccessor(targetClass);

            result = ma.callStaticMethod(context, targetClass, methodName, args);
            return result;
        } catch (ClassNotFoundException ex) {
            throw new MethodFailedException(className, methodName, ex);
        }
    }

    public static final Object callMethod( ExprContext context, Object target, String methodName, String propertyName, Object[] args ) throws ExprException, MethodFailedException
    {
        Object          result;

        if (target != null) {
            MethodAccessor  ma = getMethodAccessor(target.getClass());

            result = ma.callMethod(context, target, methodName, args);
        } else {
            throw new NullPointerException("target is null for method " + methodName);
        }
        return result;
    }

    public static final Object callConstructor( ExprContext context, String className, Object[] args ) throws ExprException
    {
        Throwable       reason = null;
        Object[]        actualArgs = args;

        try
          {
            Constructor     ctor = null;
            Class[]         ctorParameterTypes = null;
            Class           target = classForName(context, className);
            List            constructors = getConstructors(target);

            for (int i = 0, icount = constructors.size(); i < icount; i++)
            {
                Constructor     c = (Constructor)constructors.get(i);
                Class[]         cParameterTypes = getParameterTypes(c);

                if ( areArgsCompatible(args, cParameterTypes) && (ctor == null || isMoreSpecific(cParameterTypes, ctorParameterTypes)) ) {
                    ctor = c;
                    ctorParameterTypes = cParameterTypes;
                }
              }
            if ( ctor == null )
            {
                actualArgs = objectArrayPool.create(args.length);
                if ((ctor = getConvertedConstructorAndArgs( context, target, constructors, args, actualArgs )) == null) {
                    throw new NoSuchMethodException();
                }
            }
            if (!context.getMemberAccess().isAccessible(context, target, ctor, null)) {
                throw new IllegalAccessException("access denied to " + target.getName() + "()");
            }
            return ctor.newInstance( actualArgs );
          }
        catch (ClassNotFoundException e)
          { reason = e; }
        catch (NoSuchMethodException e)
          { reason = e; }
        catch (IllegalAccessException e)
          { reason = e; }
        catch (InvocationTargetException e)
          { reason = e.getTargetException(); }
        catch (InstantiationException e)
          { reason = e; }
        finally {
            if (actualArgs != args) {
                objectArrayPool.recycle(actualArgs);
            }
        }

        throw new MethodFailedException( className, "new", reason );
    }

    public static final Object getMethodValue(ExprContext context, Object target, String propertyName) throws ExprException, IllegalAccessException, NoSuchMethodException, IntrospectionException
    {
        return getMethodValue(context, target, propertyName, false);
    }

    /**
        If the checkAccessAndExistence flag is true this method will check to see if the
        method exists and if it is accessible according to the context's MemberAccess.
        If neither test passes this will return NotFound.
     */
    public static final Object getMethodValue(ExprContext context, Object target, String propertyName, boolean checkAccessAndExistence) throws ExprException, IllegalAccessException, NoSuchMethodException, IntrospectionException
    {
        Object              result = null;
        Method              m = getGetMethod(context, (target == null) ? null : target.getClass(), propertyName);

        if (checkAccessAndExistence) {
            if ((m == null) || !context.getMemberAccess().isAccessible(context, target, m, propertyName)) {
                result = NotFound;
            }
        }
        if (result == null) {
            if (m != null)
            {
                try
                {
                    result = invokeMethod(target, m, NoArguments);
                }
                catch (InvocationTargetException ex)
                {
                    throw new ExprException(propertyName, ex.getTargetException());
                }
            } else {
                throw new NoSuchMethodException(propertyName);
            }
        }
        return result;
    }

    public static final boolean setMethodValue(ExprContext context, Object target, String propertyName, Object value) throws ExprException, IllegalAccessException, NoSuchMethodException, MethodFailedException, IntrospectionException
    {
        return setMethodValue(context, target, propertyName, value, false);
    }

    public static final boolean setMethodValue(ExprContext context, Object target, String propertyName, Object value, boolean checkAccessAndExistence) throws ExprException, IllegalAccessException, NoSuchMethodException, MethodFailedException, IntrospectionException
    {
        boolean     result = true;
        Method      m = getSetMethod(context, (target == null) ? null : target.getClass(), propertyName);

        if (checkAccessAndExistence) {
            if ((m == null) || !context.getMemberAccess().isAccessible(context, target, m, propertyName)) {
                result = false;
            }
        }
        if (result) {
            if (m != null) {
                Object[]        args = objectArrayPool.create(value);

                try {
                    internalCallAppropriateMethod(context, target, target, m.getName(), propertyName, Collections.nCopies(1, m), args);
                } finally {
                    objectArrayPool.recycle(args);
                }
            } else {
                result = false;
            }
        }
        return result;
    }

    public static final List getConstructors(Class targetClass)
    {
        List        result;

        synchronized(constructorCache) {
            if ((result = (List)constructorCache.get(targetClass)) == null) {
                constructorCache.put(targetClass, result = Arrays.asList(targetClass.getConstructors()));
            }
        }
        return result;
    }

    public static final Map getMethods( Class targetClass, boolean staticMethods )
    {
        ClassCache  cache = (staticMethods ? staticMethodCache : instanceMethodCache);
        Map         result;

        synchronized(cache) {
            if ((result = (Map)cache.get(targetClass)) == null)
            {
                cache.put(targetClass, result = new HashMap(23));
                for (Class c = targetClass; c != null; c = c.getSuperclass()) {
                    Method[]        ma = c.getDeclaredMethods();

                    for (int i = 0, icount = ma.length; i < icount; i++)
                    {
                        if (Modifier.isStatic(ma[i].getModifiers()) == staticMethods) {
                            List        ml = (List)result.get(ma[i].getName());

                            if (ml == null)
                                result.put(ma[i].getName(), ml = new ArrayList());
                            ml.add(ma[i]);
                        }
                    }
                }
            }
        }
        return result;
    }

    public static final List getMethods( Class targetClass, String name, boolean staticMethods )
    {
        return (List)getMethods(targetClass, staticMethods).get(name);
    }

    public static final Map getFields(Class targetClass)
    {
        Map         result;

        synchronized(fieldCache) {
            if ((result = (Map)fieldCache.get(targetClass)) == null)
            {
                Field       fa[];

                result = new HashMap(23);
                fa = targetClass.getDeclaredFields();
                for (int i = 0; i < fa.length; i++) {
                    result.put(fa[i].getName(), fa[i]);
                }
                fieldCache.put(targetClass, result);
            }
        }
        return result;
    }

    public static final Field getField(Class inClass, String name)
    {
        Field       result = null;

        synchronized(fieldCache)
        {
            Object      o = getFields(inClass).get(name);

            if (o == null)
            {
                superclasses.clear();
                for (Class sc = inClass; (sc != null) && (result == null); sc = sc.getSuperclass())
                {
                    if ((o = getFields(sc).get(name)) == NotFound)
                        break;
                    superclasses.add(sc);
                    if ((result = (Field)o) != null)
                        break;
                }
                /*
                    Bubble the found value (either cache miss or actual field)
                    to all supeclasses that we saw for quicker access next time.
                */
                for (int i = 0, icount = superclasses.size(); i < icount; i++)
                {
                    getFields((Class)superclasses.get(i)).put(name, (result == null) ? NotFound : result);
                }
            }
            else
            {
                if (o instanceof Field)
                {
                    result = (Field)o;
                }
                else
                {
                    if (result == NotFound)
                        result = null;
                }
            }
        }
        return result;
    }

    public static final Object getFieldValue(ExprContext context, Object target, String propertyName) throws NoSuchFieldException
    {
        return getFieldValue(context, target, propertyName, false);
    }

    public static final Object getFieldValue(ExprContext context, Object target, String propertyName, boolean checkAccessAndExistence) throws NoSuchFieldException
    {
        Object          result = null;
        Field           f = getField((target == null) ? null : target.getClass(), propertyName);

        if (checkAccessAndExistence) {
            if ((f == null) || !context.getMemberAccess().isAccessible(context, target, f, propertyName)) {
                result = NotFound;
            }
        }
        if (result == null) {
            if (f == null) {
                throw new NoSuchFieldException(propertyName);
            } else {
                try
                {
                    Object      state = null;

                    if ((f != null) && !Modifier.isStatic(f.getModifiers()))
                    {
                        state = context.getMemberAccess().setup(context, target, f, propertyName);
                        result = f.get(target);
                        context.getMemberAccess().restore(context, target, f, propertyName, state);
                    }
                    else
                        throw new NoSuchFieldException(propertyName);
                }
                catch (IllegalAccessException ex)
                {
                    throw new NoSuchFieldException(propertyName);
                }
            }
        }
        return result;
    }

    public static final boolean setFieldValue(ExprContext context, Object target, String propertyName, Object value) throws ExprException
    {
        boolean         result = false;

        try
        {
            Field       f = getField( (target == null) ? null : target.getClass(), propertyName );
            Object      state;

            if ((f != null) && !Modifier.isStatic(f.getModifiers()))
            {
                state = context.getMemberAccess().setup(context, target, f, propertyName);
                try
                {
                    if (isTypeCompatible(value, f.getType()) || ((value = getConvertedType( context, target, f, propertyName, value, f.getType())) != null)) {
                        f.set(target, value);
                        result = true;
                    }
                }
                finally
                {
                    context.getMemberAccess().restore(context, target, f, propertyName, state);
                }
            }
        }
        catch (IllegalAccessException ex)
        {
            throw new NoSuchPropertyException(target, propertyName, ex);
        }
        return result;
    }

    public static final boolean isFieldAccessible(ExprContext context, Object target, Class inClass, String propertyName)
    {
        return isFieldAccessible(context, target, getField(inClass, propertyName), propertyName);
    }

    public static final boolean isFieldAccessible(ExprContext context, Object target, Field field, String propertyName)
    {
        return context.getMemberAccess().isAccessible(context, target, field, propertyName);
    }

    public static final boolean hasField(ExprContext context, Object target, Class inClass, String propertyName)
    {
        Field       f = getField(inClass, propertyName);

        return (f != null) && isFieldAccessible(context, target, f, propertyName);
    }

    public static final void setStaticField( ExprContext context,
                                               String className,
                                               String fieldName,
                                               Object value)
        throws ExprException
    {
        Exception reason = null;
        try {
            Class c = classForName(context, className);

            /*
                Check for virtual static field "class"; this cannot interfere with
                normal static fields because it is a reserved word.
             */
            if (fieldName.equals("class")) {
                throw new ExprException("Field 'class' cannot be modified.");
            }
            else
            {
                Field f = c.getField(fieldName);
                if ( !Modifier.isStatic(f.getModifiers()) ) {
                    throw new ExprException(
                        "Field " + fieldName + " of class " + className + " is not static." );
                }
                if (Modifier.isFinal(f.getModifiers())) {
                     throw new ExprException(
                        "Field " + fieldName + " of class " + className + " is final." );
                }
                f.set(null, value);
            }
          }
        catch (ClassNotFoundException e)
          { reason = e; }
        catch (NoSuchFieldException e)
          { reason = e; }
        catch (SecurityException e)
          { reason = e; }
        catch (IllegalAccessException e)
          { reason = e; }

        throw new ExprException( "Could not set static field " + fieldName + " from class " + className, reason );
    }

    public static final Object getStaticField( ExprContext context, String className, String fieldName ) throws ExprException
    {
        Exception reason = null;
        try
          {
            Class c = classForName(context, className);

            /*
                Check for virtual static field "class"; this cannot interfere with
                normal static fields because it is a reserved word.
             */
            if (fieldName.equals("class"))
              {
                return c;
              }
            else
              {
                Field f = c.getField(fieldName);
                if ( !Modifier.isStatic(f.getModifiers()) )
                    throw new ExprException( "Field " + fieldName + " of class " + className + " is not static" );
                return f.get(null);
              }
          }
        catch (ClassNotFoundException e)
          { reason = e; }
        catch (NoSuchFieldException e)
          { reason = e; }
        catch (SecurityException e)
          { reason = e; }
        catch (IllegalAccessException e)
          { reason = e; }

        throw new ExprException( "Could not get static field " + fieldName + " from class " + className, reason );
    }

    public static final List getDeclaredMethods(Class targetClass, String propertyName, boolean findSets)
    {
        List        result = null;
        ClassCache  cache = declaredMethods[findSets ? 0 : 1];

        synchronized(cache) {
            Map         propertyCache = (Map)cache.get(targetClass);

            if ((propertyCache == null) || ((result = (List)propertyCache.get(propertyName)) == null)) {
                String      baseName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
                int         len = baseName.length();

                for (Class c = targetClass; c != null; c = c.getSuperclass()) {
                    Method[]        methods = c.getDeclaredMethods();

                    for (int i = 0; i < methods.length; i++) {
                        String      ms = methods[i].getName();

                        if (ms.endsWith(baseName)) {
                            boolean     isSet = false,
                                        isGet = false,
                                        isIs = false;

                            if ((isSet = ms.startsWith(SET_PREFIX)) || (isGet = ms.startsWith(GET_PREFIX)) || (isIs = ms.startsWith(IS_PREFIX))) {
                                int     prefixLength = (isIs ? 2 : 3);

                                if (isSet == findSets) {
                                    if (baseName.length() == (ms.length() - prefixLength)) {
                                        if (result == null) {
                                            result = new ArrayList();
                                        }
                                        result.add(methods[i]);
                                    }
                                }
                            }
                        }
                    }
                }
                if (propertyCache == null) {
                    cache.put(targetClass, propertyCache = new HashMap(101));
                }
                propertyCache.put(propertyName, (result == null) ? NotFoundList : result);
            }
            return (result == NotFoundList) ? null : result;
        }
    }

    public static final Method getGetMethod(ExprContext context, Class targetClass, String propertyName) throws IntrospectionException, ExprException
    {
        Method              result = null;
        PropertyDescriptor  pd = getPropertyDescriptor(targetClass, propertyName);

        if (pd == null) {
            List        methods = getDeclaredMethods(targetClass, propertyName, false /* find 'get' methods */);

            if (methods != null) {
                for (int i = 0, icount = methods.size(); i < icount; i++) {
                    Method      m = (Method)methods.get(i);
                    Class[]     mParameterTypes = getParameterTypes(m);

                    if (mParameterTypes.length == 0) {
                        result = m;
                        break;
                    }
                }
            }
        } else {
            result = pd.getReadMethod();
        }
        return result;
    }

    public static final boolean isMethodAccessible(ExprContext context, Object target, Method method, String propertyName)
    {
        return (method == null) ? false : context.getMemberAccess().isAccessible(context, target, method, propertyName);
    }

    public static final boolean hasGetMethod(ExprContext context, Object target, Class targetClass, String propertyName) throws IntrospectionException, ExprException
    {
        return isMethodAccessible(context, target, getGetMethod(context, targetClass, propertyName), propertyName);
    }

    public static final Method getSetMethod(ExprContext context, Class targetClass, String propertyName) throws IntrospectionException, ExprException
    {
        Method              result = null;
        PropertyDescriptor  pd = getPropertyDescriptor(targetClass, propertyName);

        if (pd == null) {
            List        methods = getDeclaredMethods(targetClass, propertyName, true /* find 'set' methods */);

            if (methods != null) {
                for (int i = 0, icount = methods.size(); i < icount; i++) {
                    Method      m = (Method)methods.get(i);
                    Class[]     mParameterTypes = getParameterTypes(m);

                    if (mParameterTypes.length == 1) {
                        result = m;
                        break;
                    }
                }
            }
        } else {
            result = pd.getWriteMethod();
        }
        return result;
    }

    public static final boolean hasSetMethod(ExprContext context, Object target, Class targetClass, String propertyName) throws IntrospectionException, ExprException
    {
        return isMethodAccessible(context, target, getSetMethod(context, targetClass, propertyName), propertyName);
    }

    public static final boolean hasGetProperty( ExprContext context, Object target, Object oname ) throws IntrospectionException, ExprException
    {
        Class       targetClass = (target == null) ? null : target.getClass();
        String      name = oname.toString();

        return hasGetMethod( context, target, targetClass, name ) || hasField( context, target, targetClass, name );
    }

    public static final boolean hasSetProperty( ExprContext context, Object target, Object oname ) throws IntrospectionException, ExprException
    {
        Class       targetClass = (target == null) ? null : target.getClass();
        String      name = oname.toString();

        return hasSetMethod( context, target, targetClass, name ) || hasField( context, target, targetClass, name );
    }

    private static final boolean indexMethodCheck(List methods)
    {
        boolean         result = false;

        if (methods.size() > 0) {
            Method          fm = (Method)methods.get(0);
            Class[]         fmpt = getParameterTypes(fm);
            int             fmpc = fmpt.length;
            Class           lastMethodClass = fm.getDeclaringClass();

            result = true;
            for (int i = 1; result && (i < methods.size()); i++) {
                Method      m = (Method)methods.get(i);
                Class       c = m.getDeclaringClass();

                // Check to see if more than one method implemented per class
                if (lastMethodClass == c) {
                    result = false;
                } else {
                    Class[]     mpt = getParameterTypes(fm);
                    int         mpc = fmpt.length;

                    if (fmpc != mpc) {
                        result = false;
                    }
                    for (int j = 0; j < fmpc; j++) {
                        if (fmpt[j] != mpt[j]) {
                            result = false;
                            break;
                        }
                    }
                }
                lastMethodClass = c;
            }
        }
        return result;
    }

    private static final void findObjectIndexedPropertyDescriptors(Class targetClass, Map intoMap) throws ExprException
    {
        Map     allMethods = getMethods(targetClass, false);
        Map     pairs = new HashMap(101);

        for (Iterator it = allMethods.keySet().iterator(); it.hasNext(); ) {
            String      methodName = (String)it.next();
            List        methods = (List)allMethods.get(methodName);

            /*
                Only process set/get where there is exactly one implementation
                of the method per class and those implementations are all the
                same
             */
            if (indexMethodCheck(methods)) {
                boolean     isGet = false,
                            isSet = false;
                Method      m = (Method)methods.get(0);

                if (((isSet = methodName.startsWith(SET_PREFIX)) || (isGet = methodName.startsWith(GET_PREFIX))) && (methodName.length() > 3)) {
                    String      propertyName = Introspector.decapitalize(methodName.substring(3));
                    Class[]     parameterTypes = getParameterTypes(m);
                    int         parameterCount = parameterTypes.length;

                    if (isGet && (parameterCount == 1) && (m.getReturnType() != Void.TYPE)) {
                        List        pair = (List)pairs.get(propertyName);

                        if (pair == null) {
                            pairs.put(propertyName, pair = new ArrayList());
                        }
                        pair.add(m);
                    }
                    if (isSet && (parameterCount == 2) && (m.getReturnType() == Void.TYPE)) {
                        List        pair = (List)pairs.get(propertyName);

                        if (pair == null) {
                            pairs.put(propertyName, pair = new ArrayList());
                        }
                        pair.add(m);
                    }
                }
            }
        }
        for (Iterator it = pairs.keySet().iterator(); it.hasNext();) {
            String      propertyName = (String)it.next();
            List        methods = (List)pairs.get(propertyName);

            if (methods.size() == 2) {
                Method      method1 = (Method)methods.get(0),
                            method2 = (Method)methods.get(1),
                            setMethod = (method1.getParameterTypes().length == 2) ? method1 : method2,
                            getMethod = (setMethod == method1) ? method2 : method1;
                Class       keyType = getMethod.getParameterTypes()[0],
                            propertyType = getMethod.getReturnType();

                if (keyType == setMethod.getParameterTypes()[0]) {
                    if (propertyType == setMethod.getParameterTypes()[1]) {
                        ObjectIndexedPropertyDescriptor     propertyDescriptor;

                        try {
                            propertyDescriptor = new ObjectIndexedPropertyDescriptor(propertyName, propertyType, getMethod, setMethod);
                        } catch (Exception ex) {
                            throw new ExprException("creating object indexed property descriptor for '" + propertyName + "' in " + targetClass, ex);
                        }
                        intoMap.put(propertyName, propertyDescriptor);
                    }
                }

            }
        }
    }

    /**
        This method returns the property descriptors for the given class as a Map
     */
    public static final Map getPropertyDescriptors(Class targetClass) throws IntrospectionException, ExprException
    {
        Map     result;

        synchronized(propertyDescriptorCache) {
            if ((result = (Map)propertyDescriptorCache.get(targetClass)) == null) {
                PropertyDescriptor[]    pda = Introspector.getBeanInfo(targetClass).getPropertyDescriptors();

                result = new HashMap(101);
                for (int i = 0, icount = pda.length; i < icount; i++) {
                    result.put(pda[i].getName(), pda[i]);
                }
                findObjectIndexedPropertyDescriptors(targetClass, result);
                propertyDescriptorCache.put(targetClass, result);
            }
        }
        return result;
    }

    /**
        This method returns a PropertyDescriptor for the given class and property name using
        a Map lookup (using getPropertyDescriptorsMap()).
     */
    public static final PropertyDescriptor getPropertyDescriptor(Class targetClass, String propertyName) throws IntrospectionException, ExprException
    {
        return (targetClass == null) ? null : (PropertyDescriptor)getPropertyDescriptors(targetClass).get(propertyName);
    }

    public static final PropertyDescriptor[] getPropertyDescriptorsArray(Class targetClass) throws IntrospectionException
    {
        PropertyDescriptor[]    result = null;

        if (targetClass != null) {
            synchronized(propertyDescriptorCache) {
                if ((result = (PropertyDescriptor[])propertyDescriptorCache.get(targetClass)) == null) {
                    propertyDescriptorCache.put(targetClass, result = Introspector.getBeanInfo(targetClass).getPropertyDescriptors());
                }
            }
        }
        return result;
    }

    /**
        Gets the property descriptor with the given name for the target class given.
        @param targetClass      Class for which property descriptor is desired
        @param name             Name of property
        @return                 PropertyDescriptor of the named property or null if
                                the class has no property with the given name
     */
    public static final PropertyDescriptor getPropertyDescriptorFromArray(Class targetClass, String name) throws IntrospectionException
    {
        PropertyDescriptor      result = null;
        PropertyDescriptor[]    pda = getPropertyDescriptorsArray(targetClass);

        for (int i = 0, icount = pda.length; (result == null) && (i < icount); i++) {
            if (pda[i].getName().compareTo(name) == 0) {
                result = pda[i];
            }
        }
        return result;
    }

    public static final void setMethodAccessor(Class cls, MethodAccessor accessor)
    {
        synchronized(methodAccessors) {
            methodAccessors.put( cls, accessor );
        }
    }

    public static final MethodAccessor getMethodAccessor( Class cls ) throws ExprException
    {
        MethodAccessor answer = (MethodAccessor)getHandler( cls, methodAccessors );
        if ( answer != null )
            return answer;
        throw new ExprException( "No method accessor for " + cls );
    }

    public static final NullHandler getNullHandler( Class cls ) throws ExprException
    {
        NullHandler answer = (NullHandler)getHandler( cls, nullHandlers );
        if ( answer != null )
            return answer;
        throw new ExprException( "No null handler for class " + cls );
    }

    public static final void setNullHandler( Class cls, NullHandler handler )
    {
        synchronized(nullHandlers) {
            nullHandlers.put( cls, handler );
        }
    }

    private static final Object getHandler( Class forClass, ClassCache handlers )
    {
        Object answer = null;

        synchronized(handlers) {
            if ((answer = handlers.get(forClass)) == null)
            {
                Class   keyFound;

                if (forClass.isArray())
                {
                    answer = handlers.get(Object[].class);
                    keyFound = null;
                }
                else
                {
                    keyFound = forClass;
                    outer:
                        for ( Class c = forClass; c != null; c = c.getSuperclass() )
                        {
                            answer = handlers.get(c);
                            if ( answer == null )
                            {
                                Class[] interfaces = c.getInterfaces();
                                for ( int index=0, count=interfaces.length; index < count; ++index )
                                {
                                    Class   iface = interfaces[index];

                                    answer = handlers.get(iface);
                                    if (answer == null)
                                    {
                                        /* Try super-interfaces */
                                        answer = getHandler(iface, handlers);
                                    }
                                    if ( answer != null )
                                    {
                                        keyFound = iface;
                                        break outer;
                                    }
                                }
                            }
                            else
                            {
                                keyFound = c;
                                break;
                            }
                        }
                }
                if ( answer != null )
                {
                    if ( keyFound != forClass )
                    {
                        handlers.put( forClass, answer );
                    }
                }
            }
        }
        return answer;
    }

    /**
        Determines the index property type, if any.  Returns <code>INDEXED_PROPERTY_NONE</code> if the
        property is not index-accessible as determined by AribaExpr or JavaBeans.  If it is indexable
        then this will return whether it is a JavaBeans indexed property, conforming to the
        indexed property patterns (returns <code>INDEXED_PROPERTY_INT</code>) or if it conforms
        to the AribaExpr arbitrary object indexable (returns <code>INDEXED_PROPERTY_OBJECT</code>).
     */
    public static final int getIndexedPropertyType( ExprContext context, Class sourceClass, String name) throws ExprException
    {
        int     result = INDEXED_PROPERTY_NONE;

        try {
            PropertyDescriptor  pd = getPropertyDescriptor(sourceClass, name);

            if (pd != null) {
                if (pd instanceof IndexedPropertyDescriptor) {
                    result = INDEXED_PROPERTY_INT;
                } else {
                    if (pd instanceof ObjectIndexedPropertyDescriptor) {
                        result = INDEXED_PROPERTY_OBJECT;
                    }
                }
            }
        } catch (Exception ex) {
            throw new ExprException("problem determining if '" + name + "' is an indexed property", ex);
        }
        return result;
    }

    public static final Object getIndexedProperty( ExprContext context, Object source, String name, Object index ) throws ExprException
    {
        Throwable       reason = null;
        Object[]        args = objectArrayPool.create(index);

        try {
            PropertyDescriptor          pd = getPropertyDescriptor((source == null) ? null : source.getClass(), name);
            Method                      m;

            if (pd instanceof IndexedPropertyDescriptor) {
                m = ((IndexedPropertyDescriptor)pd).getIndexedReadMethod();
            } else {
                if (pd instanceof ObjectIndexedPropertyDescriptor) {
                    m = ((ObjectIndexedPropertyDescriptor)pd).getIndexedReadMethod();
                } else {
                    throw new ExprException("property '" + name + "' is not an indexed property");
                }
            }
            return callMethod(context, source, m.getName(), name, args);
        } catch (ExprException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new ExprException("getting indexed property descriptor for '" + name + "'", ex);
        } finally {
            objectArrayPool.recycle(args);
        }
    }

    public static final void setIndexedProperty( ExprContext context, Object source, String name, Object index, Object value ) throws ExprException
    {
        Throwable       reason = null;
        Object[]        args = objectArrayPool.create(index, value);

        try {
            PropertyDescriptor          pd = getPropertyDescriptor((source == null) ? null : source.getClass(), name);
            Method                      m;

            if (pd instanceof IndexedPropertyDescriptor) {
                m = ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod();
            } else {
                if (pd instanceof ObjectIndexedPropertyDescriptor) {
                    m = ((ObjectIndexedPropertyDescriptor)pd).getIndexedWriteMethod();
                } else {
                    throw new ExprException("property '" + name + "' is not an indexed property");
                }
            }
            callMethod(context, source, m.getName(), name, args);
        } catch (ExprException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new ExprException("getting indexed property descriptor for '" + name + "'", ex);
        } finally {
            objectArrayPool.recycle(args);
        }
    }

    public static EvaluationPool getEvaluationPool()
    {
        return evaluationPool;
    }

    public static ObjectArrayPool getObjectArrayPool()
    {
        return objectArrayPool;
    }

    public static Object convert (Object source)
        throws ExprException
    {
        if (source == null) {
            return null;
        }
        try {
            return ConvertibleFieldValue.convert(source);
        }
        catch (Exception e)
        {
            throw new ExprException(e.getLocalizedMessage());
        }
    }
}

